package com.spontaneappnew;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.location.Location;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;

import com.parse.FindCallback;
import com.parse.ParseException;
import com.parse.ParseFile;
import com.parse.ParseGeoPoint;
import com.parse.ParseObject;
import com.parse.ParseQuery;
import com.parse.SaveCallback;

public class Places {

	private static Places instance;
	private static ArrayList<Place> allPlaces;
	private ArrayList<Place> byDatePlaces;
	private ArrayList<Categories_Places> filteredPlaces;
	private ArrayList<Category> filteredCategories;
	private static ArrayList<Comment> visitedPlacesIDs;
	private static ArrayList<Place> placesAddedIDs;
	private ArrayList<FBFriendPlace> friendsPlaces;
	private static ArrayList<String> friendsUsingApp;
	private Place currentPlace;
	private int numOfImagesUploaded;

	/*
	 * The constructor receives an int Array 5 values: Min Long, Min Lat, Max Long, Max Lat, radius. 
	 * Initializes the places singleton according to the radius set in user's shared preferences. Default is 10 KMs / Miles.
	 * Get needed entries from LatLong table. Each entry contains and array of place ids. Go through the places and fetch each place object if it's distance
	 * is in the radius. 
	 */
	private Places(int[] minMaxLatLngValues, Location mylocation){

		ParseQuery<ParseObject> LatLongCubesQuery = new ParseQuery<ParseObject>("LatLong").orderByAscending("Latitude").orderByDescending("Longitude");
		LatLongCubesQuery.whereLessThanOrEqualTo("Latitude", minMaxLatLngValues[3]).whereGreaterThanOrEqualTo("Latitude", minMaxLatLngValues[1]).whereLessThanOrEqualTo("Longitude", minMaxLatLngValues[2]).whereGreaterThanOrEqualTo("Longitude", minMaxLatLngValues[0]);
		List<ParseObject> Cubes = null;
		try {
			Cubes = LatLongCubesQuery.find();
		} catch (ParseException e) {
			e.printStackTrace();
		}
		ParseGeoPoint myLocation = new ParseGeoPoint(mylocation.getLatitude(), mylocation.getLongitude());
		Places.allPlaces = new ArrayList<Place>(); //start adding places to the array after checking that they are in the radius
		int radius = minMaxLatLngValues[4];
		ArrayList<String> ObjIDS = new ArrayList<String>();
		for (ParseObject cube : Cubes){
			JSONArray objectids = cube.getJSONArray("Places");
			int numOfIds = objectids.length();
			
			for (int i=0; i<numOfIds;i++){
				try {
					ObjIDS.add(objectids.getString(i));
				} catch (JSONException e) {
					e.printStackTrace();
				}
			}
		}
		ParseQuery<ParseObject> query = new ParseQuery<ParseObject>("Places").orderByDescending("rating,numRatings").whereWithinKilometers("location", myLocation, radius).whereContainedIn("objectId", ObjIDS);
		List<ParseObject> Places = null;
		try {
			Places = query.find();
		} catch (ParseException e) {
			e.printStackTrace();
		}
		for (ParseObject place : Places) {	
		allPlaces.add(new Place(place.getObjectId(), place.getString("name"), place.getString("description"), place.getCreatedAt(), 
				place.getString("country"), place.getString("city"), place.getString("street"),
				(float)place.getDouble("rating"), place.getParseGeoPoint("location").getLatitude(), 
				place.getParseGeoPoint("location").getLongitude(), place.getString("categoryId"), place.getString("userID"),place.getJSONArray("comments")
				,place.getInt("numOfImages"),place.getParseFile("img1"),place.getParseFile("img2"),place.getParseFile("img3"),place.getParseFile("img4")
				,place.getParseFile("img5"),place.getParseFile("img6"),place.getParseFile("tmb1"),place.getParseFile("tmb2"),place.getParseFile("tmb3")
				,place.getParseFile("tmb4"),place.getParseFile("tmb5"),place.getParseFile("tmb6")));
		
		}	
	}
	
	// Empty constructor
	public Places(){}
	
	/**
	 * @return Return existing place singleton. 
	 */
	public static Places getInstance(){
		if (instance==null)
			instance=new Places();
		return instance;
	}
	
	/**
	 * @param minMaxLatLngValues
	 * @param location
	 * @return new instance of the place singleton according to the values.
	 */
	public static Places getInitializedInstance(Location location, int radius ){
		int[] minMaxLatLngValues = getMinMaxLatLngValues(location.getLatitude(), location.getLongitude(), radius);
		instance = new Places(minMaxLatLngValues,location);
		return instance;
	}
	
	public ArrayList<Place> getAllPlaces(){
		if(allPlaces != null)
			return Places.allPlaces;
		return new ArrayList<Place>();
	}
	
	public  ArrayList<Categories_Places> getFilteredPlaces(){
		return this.filteredPlaces;
	}
	
	private void createPlaceAfterImagesLoading(int uploaded,int total, Place newPlace, ArrayList<ParseFile> imgArray
			, ArrayList<ParseFile> thumbArray){
		if (uploaded==total){
			//uploadnewPlace
			newPlace.setImagesArray(imgArray);
			newPlace.setThumbnailArray(thumbArray);
			addPlace(newPlace);
		}
	}
	
	// starts an AsyncTask to upload the images 
	public void addPlace(final Place newPlace, Uri[] imgs){
		new SavePlaceAsyncTask().execute(new Object[]{newPlace,imgs});
	}
	
	public void addPlace(final Place newPlace){
		Places.allPlaces.add(0,newPlace);
		final ParseObject parseNewPlace = new ParseObject("Places");
		parseNewPlace.put("name",newPlace.getName());
		parseNewPlace.put("description",newPlace.getDescription());
		parseNewPlace.put("country",newPlace.getCountry());
		parseNewPlace.put("city",newPlace.getCity());
		parseNewPlace.put("street",newPlace.getStreet());
		parseNewPlace.put("rating",newPlace.getRating());
		parseNewPlace.put("numRatings",1);
		parseNewPlace.put("SumOfRatings", newPlace.getRating());
		parseNewPlace.put("location",newPlace.getLocation());
		parseNewPlace.put("userID",newPlace.getUserID());
		parseNewPlace.put("categoryId",newPlace.getCategory());
		parseNewPlace.put("numOfImages",newPlace.getNumOfImages());
		for (int i=0;i<newPlace.getNumOfImages();i++){
			parseNewPlace.put("img"+(i+1), newPlace.getImagesArray().get(i));
			parseNewPlace.put("tmb"+(i+1), newPlace.getThumbnailsArray().get(i));
		}
		parseNewPlace.saveInBackground(new SaveCallback() {
			/*
			 * When saving the new place is done, the object contains an ID.
			 * The callback function done() adds the ID to the corresponding entry in the LatLong table.
			 * If entry doesn't exist, creates a new one. If exists adds the object id to the places array.
			 */
			
			@Override
			public void done(ParseException e) {
				final String objId = parseNewPlace.getObjectId();
				ParseGeoPoint Locat= parseNewPlace.getParseGeoPoint("location");
				final int latitude = (int) Math.floor(Locat.getLatitude());
				final int longitude = (int) Math.floor(Locat.getLongitude());
				final ParseQuery<ParseObject> query = new ParseQuery<ParseObject>("LatLong").whereEqualTo("Latitude", latitude).whereEqualTo("Longitude", longitude);
				query.findInBackground(new FindCallback<ParseObject>() {
					@Override
					public void done(List<ParseObject> LatLongCubes, ParseException e1) {
						if (LatLongCubes.size()==1){
							LatLongCubes.get(0).add("Places", objId);
							LatLongCubes.get(0).saveInBackground();
						}
						else {
							ParseObject newLatLong = new ParseObject("LatLong");
							newLatLong.put("Latitude", latitude);
							newLatLong.put("Longitude",longitude);
							newLatLong.add("Places",objId);
							newLatLong.saveInBackground();
						}
					}
				});
				
				final ParseQuery<ParseObject> query2 = new ParseQuery<ParseObject>("User_History").whereEqualTo("userID",parseNewPlace.get("userID"));
				query2.findInBackground(new FindCallback<ParseObject>() {
					
					@Override
					public void done(List<ParseObject> user, ParseException arg1) {
						if (user.size()==0){
							ParseObject NewUserHistory = new ParseObject("User_History");
							NewUserHistory.put("userID", parseNewPlace.get("userID"));
							NewUserHistory.add("places", objId);
							NewUserHistory.saveInBackground();
						}
						else {
							user.get(0).add("places", objId);
							user.get(0).saveInBackground();
						}
						addAdded(newPlace);
					}
				});
			}	
		});
	}
	
	public void setFriendsList(ArrayList<String> friendsList){
		Places.friendsUsingApp = friendsList;
	}
	
	public ArrayList<String> getFriendsUsingAppList(){
		return Places.friendsUsingApp;
	}
	
	/*
	 * The function goes over all the places list and for each one checks if the user is friends
	 * of the uploader of the place.
	 * Returns a list of FBFriendPlace (place + FB name of the friend) 
	 * friendsUsingApp array holds places in the even cells and the FB user name who uploaded it in the odd cells
	 */
	public ArrayList<FBFriendPlace> filterPlacesByFriends(){
		friendsPlaces = new ArrayList<FBFriendPlace>();
		if(friendsUsingApp != null){
			int index;
			for(Place place: allPlaces) {
				index = friendsUsingApp.indexOf(place.getUserID());
				if(index != -1){
					FBFriendPlace fbPlace = new FBFriendPlace(friendsUsingApp.get(index+1), place);
					friendsPlaces.add(fbPlace);
				}
			}
		}
		return friendsPlaces;
	}
	
/*************************************************************************************/
	public void filterPlaces(){
		this.filteredPlaces = new ArrayList<Categories_Places>();
		this.filteredCategories = new ArrayList<Category>();
		List<Category> selectedCat = Categories.getInstance().getSelectedCategories();
		if (selectedCat==null){
			//DO Nothing 
		} else {
			for (Category cat : selectedCat){
				Categories_Places temp = new Categories_Places(cat); 
				for (Place currP : Places.allPlaces){
					if (cat.getID().compareTo(currP.getCategory())==0){
					//	this.filteredPlaces.add(currP);
						temp.addPlace(currP);
					}
				}
				if (temp.getPlaceList().size()>0){
					this.filteredPlaces.add(temp);
					this.filteredCategories.add(cat);
				}
			}
		}
	}	
	
/*************************************************************************************/	
	
	public ArrayList<FBFriendPlace> getFriendsPlace(){
		return this.friendsPlaces;
	}
	
	public ArrayList<Place> getPlacesById(){
		return this.byDatePlaces;
	}
	
	/*
	 * this will hold the current place before going to the placeprofile activity for fast reference (instead of finding it in the array)
	 */
	public Place getCurrentPlace(){
		return this.currentPlace;
	}
	
	/*
	 * Sets current place for place profile activity.
	 */
	public void setCurrentPlace(int position,int numOfArrayList){
		switch (numOfArrayList) {
			case 1:
				this.currentPlace = allPlaces.get(position);
				break;
				
			case 2:
				this.currentPlace = byDatePlaces.get(position);
				break;
				
			case 3:
				this.currentPlace = friendsPlaces.get(position).getPlace();
				break;	
			default:
				break;
		}		
	}
	
	public void setCurrentPlace(int groupPos, int childPos,int numOfArrayList){
		if (numOfArrayList==4){
			currentPlace = this.filteredPlaces.get(groupPos).getPlaceList().get(childPos);
		}	
	}
	
	  private static int[] getMinMaxLatLngValues(double currLatDegrees, double currLngDegrees, int currRadius){
		   
		   int[] result = new int[5];
		   double PI = Math.PI;
		   double radius = (double)currRadius;
		   double d = radius/6378.8; // 6378.8 is Earth radius in km
		   double lat1 = (PI/180.0)* currLatDegrees;
		   double lng1 = (PI/180.0)* currLngDegrees;

		   double a = 90.0;
		   // Go around a circle from 90 to 0 degrees, every 90 degrees (4 iterations)
		   for (int i = 0; i < 4; i++) {
			   double tc = (PI/180.0)*a;
			   double y = Math.asin(Math.sin(lat1)*Math.cos(d)+Math.cos(lat1)*Math.sin(d)*Math.cos(tc));
			   double lat = y*(180.0/PI);
			   if(a % 180 == 0){
				   result[i] = (int) lat;
			   }
			   else{
				   double dlng = Math.atan2(Math.sin(tc)*Math.sin(d)*Math.cos(lat1),Math.cos(d)-Math.sin(lat1)*Math.sin(y));
				   double x = ((lng1-dlng+PI) % (2.0*PI)) - PI ;
				   double lon = x*(180.0/PI);
				   result[i] = (int) lon;
			   }
			   a = ((a + 90.0)%360.0);
		   }
		   result[4] =  currRadius;
		   return result;   
	   }
	  
	  public ArrayList<Category> getFilteredCategories(){
		  return this.filteredCategories;
	  }
	  
	  /*
	   * Gets the user's FB id and fetches the places the user added and visited (called from another AsyncTask)
	   */
	  public void getUserHistoryFromServer(String FBid){
		  visitedPlacesIDs = new ArrayList<Comment>();
		  placesAddedIDs = new ArrayList<Place>();
		  ParseQuery<ParseObject> History = new ParseQuery<ParseObject>("User_History");
		  History.whereEqualTo("userID",FBid);
		  
		  List<ParseObject> userHistory = null;	
		try {
			userHistory = History.find();
		} catch (ParseException e1) {
			e1.printStackTrace();
		}
		  if (userHistory.size()==1){
				JSONArray visited = userHistory.get(0).getJSONArray("comments");
				if (visited!=null){
		 			final int numOfIds=visited.length();
		 			for (int i=0; i<numOfIds;i++){
		 				try {
		 					ParseQuery<ParseObject> currCommentQuery = new ParseQuery<ParseObject>("Comments");
		 					ParseObject currComment = null;
							try {
								currComment = currCommentQuery.get(visited.getString(i));
							} catch (ParseException e) {
								e.printStackTrace();
							}
							Comment newCom = new Comment(currComment.getString("comment"),currComment.getDouble("rating"),currComment.getCreatedAt());
							String placeID = currComment.getString("placeID");
		 					newCom.setPlaceName(placeID);
							visitedPlacesIDs.add(newCom);
	
		 				} catch (JSONException e) {
		 					e.printStackTrace();
		 				}
		 			}
		 		}
	
				JSONArray added = userHistory.get(0).getJSONArray("places");
				if (added!=null){
		 			int numOfIds=added.length();
		 			for (int i=0; i<numOfIds;i++){
		 				ParseQuery<ParseObject>  currPlace = new ParseQuery<ParseObject>("Places");
		 				ParseObject currentPl=null;
						try {
							currentPl = currPlace.get(added.getString(i));
						} catch (ParseException e) {
							e.printStackTrace();
						} catch (JSONException e) {
							e.printStackTrace();
						}
		 				placesAddedIDs.add(new Place(currentPl));
		 			}
				}
		  }
	  }
	  
	  public void startGettingFromServer(String fbid){
		  getUserHistoryFromServer(fbid);
	  }
	  
	  public ArrayList<Comment> getVisitedPlaces(){
		  return Places.visitedPlacesIDs;
	  }

	  public ArrayList<Place> getAddedPlaces(){
		  return Places.placesAddedIDs;
	  }
	  
	  public void addAdded(Place pl){
		  Places.placesAddedIDs.add(pl);
	  }
	  
	  public void addVisited(Comment com){
		  Places.visitedPlacesIDs.add(com);
	  }
	  
	  private byte[] getByteArrayFromImage(Uri filePath) throws FileNotFoundException, IOException {
		File file = new File(filePath.getPath());
		FileInputStream fis = new FileInputStream(file);

		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		byte[] buf = new byte[3096];
		try {
		    for (int readNum; (readNum = fis.read(buf)) != -1;) {
		        bos.write(buf, 0, readNum); 
		    }
		} catch (IOException ex) {
		    Log.e("error","error");
		}
		fis.close();
		byte[] bytes = bos.toByteArray();
		return bytes;
	  }
	  
	  private class SavePlaceAsyncTask extends AsyncTask<Object[], Void, Void>{
	
			@Override
			protected Void doInBackground(final Object[]... params) {
				final ArrayList<ParseFile> imgArray = new ArrayList<ParseFile>();
				final ArrayList<ParseFile> thumbArr = new ArrayList<ParseFile>();
				int j=0;
				for (int i=0;i<6;i++){
					if (((Uri[])params[0][1])[i]!=null)
						j++;
				}
				final int num = j;
				createPlaceAfterImagesLoading(0,j,((Place)params[0][0]),imgArray,thumbArr);
				numOfImagesUploaded=0;
				for (int i=0;i<6;i++){
					if (((Uri[])params[0][1])[i]!=null){
						try {
							byte[] img = getByteArrayFromImage(((Uri[])params[0][1])[i]);
							
							//get aspect Ration
						Bitmap temp = BitmapFactory.decodeByteArray(img, 0, img.length);
						int height = temp.getHeight();
						int width = temp.getWidth();
						double aspectRatio = (double) width / (double) height;  
						int newWidth = (int) Math.floor(aspectRatio * 200);
		
						//create thumbnail
						Bitmap TMPthumbnail = Bitmap.createScaledBitmap(temp,newWidth, 200, true);
						ByteArrayOutputStream stream = new ByteArrayOutputStream();
						TMPthumbnail.compress(Bitmap.CompressFormat.PNG,100, stream);
						byte[] thumbnail = stream.toByteArray();
						
						//create new image
						newWidth = (int) Math.floor(aspectRatio * 512);
						Bitmap TMPimg = Bitmap.createScaledBitmap(temp,newWidth, 512, true);
						stream.reset();
						TMPimg.compress(Bitmap.CompressFormat.PNG,100, stream);
						byte[] bigImage = stream.toByteArray();
						
						//start uploading
						final ParseFile thumb = new ParseFile("thumbnail.png",thumbnail);
						final ParseFile image = new ParseFile("image.png",bigImage);
						image.saveInBackground(new SaveCallback() {
							
							@Override
							public void done(ParseException e) {
								thumb.saveInBackground(new SaveCallback() {
									
									@Override
									public void done(ParseException e) {
										numOfImagesUploaded++;
										imgArray.add(image);
										thumbArr.add(thumb);
										createPlaceAfterImagesLoading(numOfImagesUploaded, num, ((Place)params[0][0]), imgArray, thumbArr);
									}
								});		
							}
						});
					}
					catch (FileNotFoundException e) {
						e.printStackTrace();
					} catch (IOException e) {
						e.printStackTrace();
					} 
				}
			}
			return null;
		}
	}
}
